﻿// HSS.Interlink.UI.UploadControlViewModel.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2012
// ----------------------------------------------------------------------------
// File:       UploadControlViewModel.cs
// Author:     HSS\gbanta
// Created:    01/17/2012
// Modified:   01/17/2012
// ----------------------------------------------------------------------------
namespace HSS.Interlink.UI
{
	#region Using Directives
	using System;
	using System.Collections.Generic;
	using System.Collections.ObjectModel;
	using System.IO;
	using System.Linq;
	using System.Windows;
	using System.Windows.Browser;
	using System.Windows.Controls;
	using System.Windows.Threading;

	#endregion

	#region UploadControlViewModel
	/// <summary>
	/// UploadControlViewModel
	/// </summary>
	[ScriptableType]
	public sealed class UploadControlViewModel : System.ComponentModel.INotifyPropertyChanged
	{
		#region Events

		#region StateChanged
		/// <summary>
		/// Occurs when the State property changes. This is for Javascript integration.
		/// </summary>
		[ScriptableMember]
		public event EventHandler<StateChangedEventArgs> StateChanged;
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void OnStateChanged(string newState)
		{
			try
			{
				var handler = StateChanged;
				if (null != handler)
					handler(this, new StateChangedEventArgs(newState));
			}
			catch { }
		}
		#endregion

		#region UploadProgressChanged
		/// <summary>
		/// Occurs when the upload progress changes.
		/// </summary>
		[ScriptableMember]
		public event EventHandler<UploadManagerProgressEventArgs> UploadProgressChanged;
		private void OnUploadProgressChanged(UploadManagerProgressEventArgs args)
		{
			var handler = UploadProgressChanged;
			if (null != handler)
				handler(null, args);
		}
		#endregion

		#region UploadCompleted
		/// <summary>
		/// Occurs when the upload completes.
		/// </summary>
		[ScriptableMember]
		public event EventHandler UploadCompleted;
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private void OnUploadCompleted(EventArgs e)
		{
			try
			{
				var handler = UploadCompleted;
				if (null != handler)
					handler(this, e);
			}
			catch (Exception ex)
			{
				Alert(ex.Message);
			}
		}
		#endregion

		#region FileAdded
		/// <summary>
		/// Occurs when a file is added.
		/// </summary>
		[ScriptableMember]
		public event EventHandler<UploadManagerFileChangedEventArgs> FileAdded;
		private void OnFileAdded(UploadManagerFileChangedEventArgs args)
		{
			var handler = this.FileAdded;
			if (null != handler)
				handler(this, args);
		}
		#endregion

		#region FileRemoved
		/// <summary>
		/// Occurs when a file is removed.
		/// </summary>
		[ScriptableMember]
		public event EventHandler<UploadManagerFileChangedEventArgs> FileRemoved;
		private void OnFileRemoved(UploadManagerFileChangedEventArgs args)
		{
			var handler = this.FileRemoved;
			if (null != handler)
				handler(this, args);
		}
		#endregion

		#region FileChanged
		/// <summary>
		/// Occurs when a file has changes.
		/// </summary>
		[ScriptableMember]
		public event EventHandler<UploadManagerFileChangedEventArgs> FileChanged;
		private void OnFileChanged(UploadManagerFileChangedEventArgs args)
		{
			var handler = this.FileChanged;
			if (null != handler)
				handler(this, args);
		}
		#endregion

		#region GetMetadata
		/// <summary>
		/// An event that is raised just before a file is uploaded,
		/// allowing you to provide metadata to be sent along with
		/// the file, such as a custom file id or an associated
		/// record id or any string that you want to pass.
		/// </summary>
		public event EventHandler<GetMetadataEventArgs> GetMetadata;
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		private string OnGetMetadata(string fileName)
		{
			var metaData = string.Empty;
			try
			{
				var e = new GetMetadataEventArgs(fileName);

				// Try HTML First.
				if (HtmlPage.IsEnabled)
				{
					try
					{
						var result = HtmlPage.Window.Invoke("GetMetadata", fileName);
						if (null != result)
							e.Metadata = result.ToString();
					}
					catch { }
				}

				var handler = this.GetMetadata;
				if (null != handler)
				{
					handler(this, e);
					metaData = e.Metadata;
				}
			}
			catch (Exception ex)
			{
				Alert(ex.Message);
			}
			return metaData;
		}
		#endregion

		#region ViewFileDetails
		/// <summary>
		/// Occurs when the user selects the View Details context menu of a particular file
		/// requesting to show a dialog or child window displaying the details of that file.
		/// </summary>
		public event EventHandler<ViewFileDetailsEventArgs> ViewFileDetails;
		private void OnViewFileDetails(UploadFile file)
		{
			var handler = ViewFileDetails;
			if (null != handler)
				handler(this, new ViewFileDetailsEventArgs(file));
		}
		#endregion

		#endregion

		#region Fields
		List<string> excludedExt = new List<string>();
		UploadManager manager;
		IUploadConfiguration config;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="configuration">An <see cref="IUploadConfiguration"/> source.</param>
		/// <param name="uiDispatcher">The UI Dispatcher to raise events on.</param>
		public UploadControlViewModel(IUploadConfiguration configuration, Dispatcher uiDispatcher)
		{
			if (null == configuration)
				throw new ArgumentNullException("configuration");
			this.config = configuration;
			this.manager = new UploadManager(uiDispatcher, configuration.MaximumConcurrency);
			this._state = UploadState.NotStarted;

			if (System.ComponentModel.DesignerProperties.IsInDesignTool)
				return;

			this.Initialize();
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void Initialize()
		{
			this.BrowseCommand = new InterlinkCommand(this.Browse, this.CanBrowse);
			this.ResetCommand = new InterlinkCommand(this.Reset, this.CanReset);
			this.CancelCommand = new InterlinkCommand(this.CancelAsync, this.CanCancel);
			this.UploadCommand = new InterlinkCommand(this.UploadAsync, this.CanUpload);

			this.manager.UploadCompleted += Manager_UploadCompleted;
			this.manager.UploadProgressChanged += Manager_UploadProgressChanged;
			this.manager.FileAdded += Manager_FileAdded;
			this.manager.FileRemoved += Manager_FileRemoved;
			this.manager.FileChanged += Manager_FileChanged;

			if (HtmlPage.IsEnabled)
			{
				HtmlPage.RegisterScriptableObject("Interlink", this);
				try
				{
					HtmlPage.Window.Invoke("InitializeInterlink");
				}
				catch { }
			}
		}
		#endregion

		#region Properties

		#region Configuration

		#region AllowFileOverwrite
		/// <summary>
		/// Gets or sets whether or not to overwrite existing files.
		/// </summary>
		[ScriptableMember]
		public bool AllowFileOverwrite
		{
			get { return this.config.AllowFileOverwrite; }
			set { this.config.AllowFileOverwrite = value; }
		}
		#endregion

		#region AllowMultiSelect
		/// <summary>
		/// Gets or sets whether or not the user can select multiple files (Default: True).
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi"), ScriptableMember]
		public bool AllowMultiSelect
		{
			get { return this.config.AllowMultiSelect; }
			set { this.config.AllowMultiSelect = value; }
		}
		#endregion

		#region AllowNewUpload
		/// <summary>
		/// Gets or sets whether or not once the dialog is shown, if the User can initiate a new Upload.
		/// </summary>
		[ScriptableMember]
		public bool AllowNewUpload
		{
			get { return this.config.AllowNewUpload; }
			set { this.config.AllowNewUpload = value; }
		}
		#endregion

		#region AutoUpload
		/// <summary>
		/// Gets or sets whether or not to begin uploading immediately after file selection (Default: false).
		/// </summary>
		/// <remarks><b>Default:</b> false</remarks>
		[ScriptableMember]
		public bool AutoUpload
		{
			get { return this.config.AutoUpload; }
			set { this.config.AutoUpload = value; }
		}
		#endregion

		#region ClientConfig
		/// <summary>
		/// Gets or sets the configuration string to provide to the UploadClient during intialization.
		/// </summary>
		[ScriptableMember]
		public string ClientConfig
		{
			get { return this.config.ClientConfig; }
			set { this.config.ClientConfig = value; }
		}
		#endregion

		#region ClientType
		/// <summary>
		/// Gets or sets the default Client Type that will process the file upload.
		/// </summary>
		[ScriptableMember]
		public string ClientType
		{
			get { return this.config.ClientType; }
			set { this.config.ClientType = value; }
		}
		#endregion

		#region Comment
		/// <summary>
		/// Gets or sets the Comment to display to the user in the Header area.
		/// </summary>
		[ScriptableMember]
		public string Comment
		{
			get { return this.config.Comment; }
			set { this.config.Comment = value; }
		}
		#endregion

		#region ExcludeFileExtension
		/// <summary>
		/// Gets or sets the pipe delimited list of File extensions that are
		/// prohibited. Example: .exe|.mdb
		/// </summary>
		[ScriptableMember]
		public string ExcludeFileExtension
		{
			get { return this.config.ExcludeFileExtension; }
			set { this.config.ExcludeFileExtension = value; }
		}
		#endregion

		#region Filters
		/// <summary>
		/// Gets or sets the File filters for the Browse Dialog.
		/// </summary>
		[ScriptableMember]
		public string Filters
		{
			get { return this.config.Filters; }
			set { this.config.Filters = value; }
		}
		#endregion

		#region FolderName
		/// <summary>
		/// Gets or sets the Title to be displayed in the file header area.
		/// </summary>
		[ScriptableMember]
		public string Title
		{
			get { return this.config.Title; }
			set { this.config.Title = value; }
		}
		#endregion

		#region MaximumConcurrency
		/// <summary>
		/// Gets or sets the Maximum concurrent uploads. Default: 2
		/// </summary>
		[ScriptableMember]
		public int MaximumConcurrency
		{
			get { return this.config.MaximumConcurrency; }
			set
			{
				this.config.MaximumConcurrency = value;
				this.manager.MaximumConcurrentUpload = value;
			}
		}
		#endregion

		#region MaximumFileSize
		/// <summary>
		/// Gets or sets the Maximum size, in bytes, of a given file that can be uploaded.
		/// The default is Int32.MaxValue.
		/// </summary>
		[ScriptableMember]
		public int MaximumFileSize
		{
			get { return this.config.MaximumFileSize; }
			set { this.config.MaximumFileSize = value; }
		}
		#endregion

		#region NoFileText
		/// <summary>
		/// Gets or sets the text to be displayed when no files have been selected or
		/// when the list is cleared.
		/// </summary>
		[ScriptableMember]
		public string NoFilesText
		{
			get { return this.config.NoFilesText; }
			set { this.config.NoFilesText = value; }
		}
		#endregion

		#region ServiceUri
		/// <summary>
		/// Gets or sets the uri (absolute or relative) to the upload http handler. For OOB must be the absolute Uri. Default: '/FileUpload.ashx'.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), ScriptableMember]
		public string ServiceUri
		{
			get { return this.config.ServiceUri; }
			set
			{
				try
				{
					FileQuery.ResolveUrl(value);
					this.config.ServiceUri = value;
				}
				catch (Exception ex)
				{
					Alert(ex.Message);
				}
			}
		}
		#endregion

		#region UploadFileType
		/// <summary>
		/// Gets or sets the fully qualified name of the Type of the model that contains an individual file for upload and must be or derive from HSS.Interlink.UI.UploadFile.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This property is used to resolve the desired upload file model type. Which
		/// can be the default <see cref="UploadFile"/> or a derived type allowing you
		/// extend the <see cref="UploadFile"/> model.
		/// </para>
		/// <para>
		/// The default is 'HSS.Interlink.UI.UploadFile'.
		/// </para>
		/// </remarks>
		[ScriptableMember]
		public string UploadFileType
		{
			get { return this.config.UploadFileType; }
			set { this.config.UploadFileType = value; }
		}
		#endregion

		#region UploadFileDataTemplate
		/// <summary>
		/// Gets or sets the UploadFileDataTemplate value.
		/// </summary>
		[ScriptableMember]
		public string UploadFileDataTemplate
		{
			get { return this.config.UploadFileDataTemplate; }
			set { this.config.UploadFileDataTemplate = value; }
		}
		#endregion

		#endregion

		#region Files
		/// <summary>
		/// Gets the collection of files as an <see cref="IList{T}"/> for Javascript access.
		/// </summary>
		[ScriptableMember]
		public IList<UploadFile> FileList
		{
			get
			{
				return this.manager.Files;
			}
		}
		/// <summary>
		/// Gets the observable collection of files being uploaded.
		/// </summary>
		public ObservableCollection<UploadFile> Files
		{
			get { return this.manager.Files; }
		}
		/// <summary>
		/// Gets or sets the selected file.
		/// </summary>
		public UploadFile SelectedFile
		{
			get { return _selectedFile; }
			set
			{
				_selectedFile = value;
				if (null != _selectedFile)
					_selectedFile.IsSelected = true;
				this.OnPropertyChanged("SelectedFile");
			}
		} UploadFile _selectedFile;
		#endregion

		#region State
		/// <summary>
		/// Gets the state of the upload.
		/// </summary>
		public UploadState State
		{
			get { return _state; }
			set
			{
				if (_state != value)
				{
					_state = value;
					this.OnPropertyChanged("State");
					this.OnPropertyChanged("StateString");
					this.OnPropertyChanged("StateFormatted");
					this.OnCommandStateChanged();
					this.OnStateChanged(value.ToString());
				}
			}
		} UploadState _state = UploadState.NotStarted;
		/// <summary>
		/// Gets the state as string.
		/// </summary>
		[ScriptableMember]
		public string StateString
		{
			get { return this.State.ToString(); }
		}
		/// <summary>
		/// Gets the state formatted as a friendly status (i.e., 'Upload completed').
		/// </summary>
		[ScriptableMember]
		public string StateFormatted
		{
			get
			{
				string statusStr = string.Empty;
				switch (this.State)
				{
					case UploadState.CanceledDone:
					case UploadState.Canceled:
						statusStr = "Upload was cancelled";
						break;

					case UploadState.CompletedDone:
					case UploadState.Completed:
						statusStr = "Upload completed";
						break;

					case UploadState.Pending:
						statusStr = "Ready";
						break;

					case UploadState.NotStarted:
						statusStr = "Ready";
						break;

					case UploadState.PartialCompleteDone:
					case UploadState.PartialComplete:
						statusStr = "Upload partially completed; " + (this.CanceledCount + this.ErroredCount).ToString(System.Globalization.CultureInfo.CurrentCulture) + " of " + this.Files.Count + " files failed to complete or were cancelled.";
						break;

					case UploadState.InProgress:
						statusStr = "Upload running...";
						break;
				}
				return statusStr;
			}
		}
		#endregion

		#region Percentage
		/// <summary>
		/// Gets the Percentage of the entire upload process.
		/// </summary>
		[ScriptableMember]
		public double Percentage
		{
			get { return _percentage; }
			private set
			{
				_percentage = value;
				this.OnPropertyChanged("Percentage");
			}
		} double _percentage;
		#endregion

		#region Counts
		/// <summary>
		/// Gets the count of all files.
		/// </summary>
		[ScriptableMember]
		public int TotalCount
		{
			get { return this.Files.Count; }
		}
		/// <summary>
		/// Gets the count of files that currently have a pending status
		/// </summary>
		[ScriptableMember]
		public int PendingCount
		{
			get { return this.Files.Count(f => f.State == FileState.Pending); }
		}
		/// <summary>
		/// Gets the count of files that currently have a ready status
		/// </summary>
		[ScriptableMember]
		public int ReadyCount
		{
			get { return this.Files.Count(f => f.State == FileState.Ready); }
		}
		/// <summary>
		/// Gets the count of files that currently have a cancelled status
		/// </summary>
		[ScriptableMember]
		public int CanceledCount
		{
			get { return this.Files.Count(f => f.State == FileState.Canceled); }
		}
		/// <summary>
		/// Gets the count of files that currently have a finished status
		/// </summary>
		[ScriptableMember]
		public int FinishedCount
		{
			get { return this.Files.Count(f => f.State == FileState.Finished); }
		}
		/// <summary>
		/// Gets the count of files that are currently in an error state.
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Errored"), ScriptableMember]
		public int ErroredCount
		{
			get { return this.Files.Count(f => f.State == FileState.Error); }
		}
		/// <summary>
		/// Gets the count of files that currently have finished, errored, or cancelled.
		/// </summary>
		[ScriptableMember]
		public int CompletedCount
		{
			get { return this.Files.Count(f => f.State == FileState.Finished || f.State == FileState.Error || f.State == FileState.Canceled); }
		}
		#endregion

		#region Commands
		/// <summary>
		/// Gets or sets the BrowseCommand value.
		/// </summary>
		public InterlinkCommand BrowseCommand
		{
			get { return this._browseCommand; }
			set
			{
				if (this._browseCommand != value)
				{
					this._browseCommand = value;
					this.OnPropertyChanged("BrowseCommand");
				}
			}
		} InterlinkCommand _browseCommand;
		/// <summary>
		/// Gets or sets the ResetCommand value.
		/// </summary>
		public InterlinkCommand ResetCommand
		{
			get { return this._resetCommand; }
			set
			{
				if (this._resetCommand != value)
				{
					this._resetCommand = value;
					this.OnPropertyChanged("ResetCommand");
				}
			}
		} InterlinkCommand _resetCommand;
		/// <summary>
		/// Gets or sets the UploadCommand value.
		/// </summary>
		public InterlinkCommand UploadCommand
		{
			get { return this._uploadCommand; }
			set
			{
				if (this._uploadCommand != value)
				{
					this._uploadCommand = value;
					this.OnPropertyChanged("UploadCommand");
				}
			}
		} InterlinkCommand _uploadCommand;
		/// <summary>
		/// Gets or sets the CancelCommand value.
		/// </summary>
		public InterlinkCommand CancelCommand
		{
			get { return this._cancelCommand; }
			set
			{
				if (this._cancelCommand != value)
				{
					this._cancelCommand = value;
					this.OnPropertyChanged("CancelCommand");
				}
			}
		} InterlinkCommand _cancelCommand;
		#endregion

		#endregion

		#region Actions

		void OnCommandStateChanged()
		{
			this.BrowseCommand.OnCanExecuteChanged();
			this.ResetCommand.OnCanExecuteChanged();
			this.UploadCommand.OnCanExecuteChanged();
			this.CancelCommand.OnCanExecuteChanged();
		}

		#region Browse
		bool CanBrowse()
		{
			var canBrowse = (this.State != UploadState.CanceledDone && this.State != UploadState.CompletedDone && this.State != UploadState.PartialCompleteDone);
			if (canBrowse)
				canBrowse = (this.State == UploadState.NotStarted || this.State == UploadState.Pending) || (this.Files.Count == 0 && this.State == UploadState.PendingSingleFile);
			return canBrowse;
		}
		/// <summary>
		/// 
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), ScriptableMember]
		public void Browse()
		{
			if (!this.CanBrowse())
				return;

			try
			{
				var dialog = new OpenFileDialog();
				dialog.Multiselect = this.AllowMultiSelect;
				#region Apply the FileFilter
				try
				{
					if (!string.IsNullOrEmpty(this.Filters))
						dialog.Filter = this.Filters;
					else
						dialog.Filter = "All Files (*.*)|*.*";
				}
				catch
				{
					dialog.Filter = "All Files (*.*)|*.*";
				}
				#endregion

				if (dialog.ShowDialog() == true)
				{
					this.AddFiles(dialog.Files);
					if (this.AutoUpload && this.Files.Count > 0)
						this.UploadAsync();
				}
			}
			catch (Exception ex)
			{
				Alert(ex.Message);
			}
		}
		#endregion

		#region Upload
		bool CanUpload()
		{
			return this.ReadyCount > 0;
		}
		/// <summary>
		/// 
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), ScriptableMember]
		public void UploadAsync()
		{
			if (!this.CanUpload())
			{
				Alert(Strings.NoFilesSelected);
				return;
			}
			try
			{
				this.manager.UploadAsync();
				this.State = UploadState.InProgress;
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}
		}
		#endregion

		#region Reset
		bool CanReset()
		{
			return this.Files.Count > 0 && (this.State != UploadState.InProgress && this.State != UploadState.CanceledDone && this.State != UploadState.CompletedDone && this.State != UploadState.PartialCompleteDone && this.State != UploadState.PendingSingleFile);
		}
		/// <summary>
		/// 
		/// </summary>
		[ScriptableMember]
		public void Reset()
		{
			if (this.CanReset())
			{
				this.manager.Reset();
				if (this.State != UploadState.CanceledDone && this.State != UploadState.CompletedDone && this.State != UploadState.PartialCompleteDone)
					this.State = UploadState.NotStarted;
				this.Percentage = 0;
			}
		}
		#endregion

		#region RemoveAt
		/// <summary>
		/// Removes a file by the index provided.
		/// </summary>
		/// <param name="index">The index of the file to remove.</param>
		[ScriptableMember]
		public void RemoveAt(int index)
		{
			if (this.CanReset())
			{
				if (index >= 0 && this.manager.Files.Count > 0)
				{
					if (index <= this.manager.Files.Count - 1)
						this.manager.Files.RemoveAt(index);

					if (this.manager.Files.Count == 0)
					{
						// If they removed the last file, same logic as if Reset was called.
						if (this.State != UploadState.CanceledDone && this.State != UploadState.CompletedDone && this.State != UploadState.PartialCompleteDone)
							this.State = UploadState.NotStarted;
						this.Percentage = 0;
					}
				}
			}
		}
		#endregion

		#region Cancel
		bool CanCancel()
		{
			return this._state == UploadState.InProgress;
		}
		/// <summary>
		/// 
		/// </summary>
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes"), ScriptableMember]
		public void CancelAsync()
		{
			if (this.CanCancel())
			{
				try
				{
					this.manager.CancelAsync();
				}
				catch (Exception ex)
				{
					Alert(ex.Message);
				}
			}
		}
		#endregion

		#endregion

		#region Methods

		#region MessageBox Helpers
		private static void Alert(string message)
		{
			if (!Application.Current.IsRunningOutOfBrowser && HtmlPage.IsEnabled)
				System.Windows.Browser.HtmlPage.Window.Alert(message);
			else
				MessageBox.Show(message, Strings.Title_FileUpload, MessageBoxButton.OK);
		}
		private static void ShowFileAlreadySelected(string fileName)
		{
			var msg = string.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.FileAlreadySelected, fileName);
			Alert(msg);
		}
		private static void ShowFileRestricted(string fileName)
		{
			var msg = string.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.FileNotAllowed, fileName);
			Alert(msg);
		}
		private void ShowFileTooLarge(string fileName)
		{
			var msg = string.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.FileTooLarge, fileName, (this.MaximumFileSize));
			Alert(msg);
		}
		private static void ShowFileTooSmall(string fileName)
		{
			var msg = string.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.FileTooSmall, fileName);
			Alert(msg);
		}
		private static void ShowFolderNotAllowed(string fileName)
		{
			var msg = string.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.FileIsFolder, fileName);
			Alert(msg);
		}
		private static void ShowMissingUploadFileType(string uploadFileType)
		{
			var msg = string.Format(System.Globalization.CultureInfo.CurrentUICulture, Strings.FileTypeUnknown, uploadFileType);
			Alert(msg);
		}
		#endregion

		#region File Helpers
		internal bool ContainsFile(string fileName)
		{
			return (0 != (from item in this.Files
						  where string.Compare(item.FileName, fileName, StringComparison.OrdinalIgnoreCase) == 0
						  select item).Count());
		}
		internal bool AddFiles(IEnumerable<FileInfo> files)
		{
			var showAlert = false;
			if (files.Count() > 1 && !this.AllowMultiSelect)
				showAlert = true;
			foreach (var file in files)
			{
				if (this.AddFile(file) && !this.AllowMultiSelect)
					break;
			}
			if (showAlert)
				Alert(Strings.OnlySingleFileAllowed);
			return this.ReadyCount > 0;
		}
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		internal bool AddFile(FileInfo file)
		{
			var fileName = file.Name;

			try
			{
				#region Validation

				// Skip Folders or Directories
				if (string.IsNullOrEmpty(file.Extension) && !file.Exists)
				{
					ShowFolderNotAllowed(fileName);
					return false;
				}

				// Skip excluded file extensions
				excludedExt = new List<string>(this.ExcludeFileExtension.Split('|'));
				if (excludedExt.Contains(file.Extension))
				{
					ShowFileRestricted(fileName);
					return false;
				}

				// Skip files already added to the list
				if (this.ContainsFile(fileName))
				{
					ShowFileAlreadySelected(fileName);
					return false;
				}

				//Check for the file size limit (configurable)
				if (file.Length > this.MaximumFileSize)
				{
					ShowFileTooLarge(fileName);
					return false;
				}

				//Check for the file size limit (too small)
				if (file.Length == 0)
				{
					ShowFileTooSmall(fileName);
					return false;
				}

				// Must have a valid UploadFileType.
				if (string.IsNullOrEmpty(this.UploadFileType))
				{
					ShowMissingUploadFileType(this.UploadFileType + "");
					return false;
				}

				#endregion

				var tc = UploadControlViewModel.ResolveType(this.UploadFileType);
				if (null == tc) throw new TypeLoadException("Unable to resolve the UploadFile Type [" + this.UploadFileType + "]");
				var uploadFile = (UploadFile)Activator.CreateInstance(tc);
				uploadFile.Initialize(file.OpenRead(), file.Name, this.ClientType, this.ClientConfig, this.ServiceUri);

				uploadFile.GetMetadataCallback = this.OnGetMetadata;
				uploadFile.ViewFileDetailCallback = this.OnViewFileDetails;
				uploadFile.AllowOverwrite = this.AllowFileOverwrite;
				uploadFile.AutoCloseStream = true;
				uploadFile.ToolTipText = file.Name;

				this.Files.Add(uploadFile);

				if (this.AllowMultiSelect)
					this.State = UploadState.Pending;
				else
					this.State = UploadState.PendingSingleFile;

				return true;
			}
			catch (Exception ex)
			{
				Alert(ex.Message);
			}

			return false;
		}
		#endregion

		internal static Type ResolveType(string assemblyQualifiedName)
		{
			var typeFullName = assemblyQualifiedName.Split(",".ToCharArray())[0];
			var type = Type.GetType(assemblyQualifiedName, false, false);
			if (null == type)
				type = Type.GetType(typeFullName, false, false);
			if (null == type)
			{
				foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
				{
					type = asm.GetType(typeFullName, false);
					if (null != type)
						break;
				}
			}
			return type;
		}

		#endregion

		#region Event Handlers
		void Manager_UploadProgressChanged(object sender, UploadManagerProgressEventArgs e)
		{
			this.Percentage = e.ProgressPercentage;
			this.OnUploadProgressChanged(e);
		}
		void Manager_UploadCompleted(object sender, EventArgs e)
		{
			var filesCount = this.Files.Count;

			if (filesCount == this.CanceledCount)
			{
				if (this.AllowNewUpload)
					this.State = UploadState.Canceled;
				else
					this.State = UploadState.CanceledDone;
			}
			else if (filesCount != this.FinishedCount)
			{
				if (this.AllowNewUpload)
					this.State = UploadState.PartialComplete;
				else
					this.State = UploadState.PartialCompleteDone;
			}
			else
			{
				if (this.AllowNewUpload)
					this.State = UploadState.Completed;
				else
					this.State = UploadState.CompletedDone;
			}

			this.OnUploadCompleted(e);
		}
		void Manager_FileAdded(object sender, UploadManagerFileChangedEventArgs e)
		{
			this.OnFileAdded(e);
		}
		void Manager_FileRemoved(object sender, UploadManagerFileChangedEventArgs e)
		{
			this.OnFileRemoved(e);
		}
		void Manager_FileChanged(object sender, UploadManagerFileChangedEventArgs e)
		{
			switch (e.PropertyName)
			{
				case "IsSelected":
					if (e.File.IsSelected)
						this.SelectedFile = e.File;
					break;
			}
			this.OnFileChanged(e);
		}
		#endregion

		#region INotifyPropertyChanged Members
		/// <summary>
		/// Event raised when a property changes.
		/// </summary>
		[ScriptableMember]
		public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
		private void OnPropertyChanged(string propertyName)
		{
			var handler = this.PropertyChanged;
			if (null != handler)
				handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
		}
		#endregion
	}
	#endregion
}